package top.wyloop.intoeins.module.iam.service.impl;

import cn.dev33.satoken.stp.StpUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.stereotype.Service;
import org.springframework.util.Assert;
import top.wyloop.intoeins.exception.SystemException;
import top.wyloop.intoeins.module.iam.model.consts.InitRoleConst;
import top.wyloop.intoeins.module.iam.model.entity.User;
import top.wyloop.intoeins.module.iam.model.entity.UserRole;
import top.wyloop.intoeins.module.iam.repository.UserRepository;
import top.wyloop.intoeins.module.iam.repository.UserRoleRepository;
import top.wyloop.intoeins.module.iam.service.PermissionService;
import top.wyloop.intoeins.module.iam.service.RoleService;
import top.wyloop.intoeins.module.iam.service.UserService;
import top.wyloop.intoeins.util.PasswordUtil;

import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Optional;

/**
 * @author Wyatt6
 * @date 2025/1/23
 */
@Slf4j
@Service
public class UserServiceImpl implements UserService {
    private static final String USERNAME_REGEXP = "^[a-zA-Z_0-9]{3,16}$";
    private static final String PASSWORD_REGEXP = "^[a-zA-Z_0-9.~!@#$%^&*?]{6,16}$";

    @Autowired
    private UserRepository userRepository;
    @Autowired
    private UserRoleRepository userRoleRepository;
    @Autowired
    private RoleService roleService;
    @Autowired
    private PermissionService permissionService;

    @Override
    public boolean checkUsernameFormat(String username) {
        log.info("[[检查用户名格式]]");
        return username.matches(USERNAME_REGEXP);
    }

    @Override
    public boolean checkPasswordFormat(String password) {
        log.info("[[检查密码格式]]");
        return password.matches(PASSWORD_REGEXP);
    }

    @Override
    public User createUser(String username, String password) {
        log.info("[[创建新用户]]");
        log.info("输入参数: username=[{}]", username);

        log.info("检查用户名是否已被注册");
        if (userRepository.findByUsername(username) != null) {
            throw new SystemException("用户名已被使用");
        }

        log.info("密码加密");
        String salt = PasswordUtil.generateSalt();
        String encryptedPassword = PasswordUtil.encode(password, salt);

        User record = new User();
        record.setId(null);
        record.setUsername(username);
        record.setPassword(encryptedPassword);
        record.setSalt(salt);
        record.setActivated(true);
        record.setCreateTime(new Date());
        User user = userRepository.save(record);
        log.info("新用户记录成功保存到数据库: id=[{}]", user.getId());

        // 注意：JPA框架save持久化并不是即时写入数据库的，
        // 因此脱敏是不能在原对象上赋值为null，
        // 否则定义为非空的属性会报错。
        log.info("用户对象脱敏后返回");
        User result = new User();
        result.setId(user.getId());
        result.setUsername(user.getUsername());
        result.setActivated(user.getActivated());
        result.setCreateTime(user.getCreateTime());
        return result;
    }

    @Override
    public User authentication(String username, String password) {
        log.info("[[身份验证]]");
        log.info("输入参数: username=[{}]", username);

        User user = userRepository.findByUsername(username);
        if (user == null || !user.getPassword().equals(PasswordUtil.encode(password, user.getSalt()))) {
            throw new SystemException("用户名或密码错误");
        }
        if (!user.getActivated()) {
            throw new SystemException("用户已冻结");
        }

        log.info("验证通过");
        return user;
    }

    @Override
    public User updateLastLoginTime(Long id) {
        log.info("[[更新用户最近登录时间]]");
        log.info("输入参数: id=[{}]", id);

        Optional<User> oldUser = userRepository.findById(id);
        if (oldUser.isPresent()) {
            User newUser = oldUser.get();
            newUser.setLastLoginTime(new Date());

            log.info("更新数据库记录");
            userRepository.save(newUser);

            return newUser;
        } else {
            throw new SystemException("用户不存在");
        }
    }

    @Override
    public void changePassword(Long id, String newPassword) {
        log.info("[[修改密码]]");
        log.info("输入参数: id=[{}]", id);

        log.info("密码加密");
        String salt = PasswordUtil.generateSalt();
        String encryptedPassword = PasswordUtil.encode(newPassword, salt);

        Optional<User> oldUser = userRepository.findById(id);
        if (oldUser.isPresent()) {
            User newUser = oldUser.get();
            newUser.setPassword(encryptedPassword);
            newUser.setSalt(salt);

            log.info("更新数据库记录");
            userRepository.save(newUser);
        } else {
            throw new SystemException("用户不存在");
        }
    }

    @Override
    public void setActivated(Long userId, Boolean activated) {
        log.info("[[设置activated值]]");
        Assert.notNull(userId, "用户ID为空");
        Assert.notNull(activated, "要设置的activated值为空");
        log.info("输入参数: userId=[{}], activated=[{}]", userId, activated);

        log.info("查询是否存在该用户");
        Optional<User> oldUser = userRepository.findById(userId);
        if (oldUser.isPresent()) {
            User newUser = oldUser.get();
            newUser.setActivated(activated);

            log.info("更新数据库");
            userRepository.save(newUser);
        } else {
            throw new SystemException("用户不存在");
        }
    }

    @Override
    public Page<User> getUsersByPageDataMaskConditionally(int pageNum,
                                                          int pageSize,
                                                          String username,
                                                          Boolean activated,
                                                          Date stCreateTime,
                                                          Date edCreateTime,
                                                          String role,
                                                          Date stLastLoginTime,
                                                          Date edLastLoginTime) {
        log.info("[[条件查询用户分页]]");
        log.info("输入参数: pageNum=[{}], pageSize=[{}], username=[{}], activated=[{}], stCreateTime=[{}], " +
                        "edCreateTime=[{}], role=[{}], stLastLoginTime=[{}], edLastLoginTime=[{}]",
                pageNum, pageSize, username, activated, stCreateTime, edCreateTime, role, stLastLoginTime, edLastLoginTime);

        Sort sort = Sort.by(Sort.Order.asc("createTime"));
        Pageable pageable = PageRequest.of(pageNum, pageSize, sort);
        Page<User> userPage = userRepository.findConditionally(
                username,
                activated,
                stCreateTime,
                edCreateTime,
                role,
                stLastLoginTime,
                edLastLoginTime,
                pageable);
        log.info("完成查询，数据脱敏");
        for (int i = 0; i < userPage.getContent().size(); i++) {
            userPage.getContent().get(i).setPassword(null);
            userPage.getContent().get(i).setSalt(null);
        }
        log.info("查询结果: {}", userPage);

        return userPage;
    }

    @Override
    public List<Long> bind(Long userId, List<Long> roleIds) {
        log.info("[[绑定]]");
        Assert.notNull(userId, "userId为空");
        Assert.notNull(roleIds, "roleIds为空");
        log.info("输入参数: userId=[{}], roleIds=[{}]", userId, roleIds);

        List<Long> failList = new ArrayList<>();
        for (Long roleId : roleIds) {
            try {
                if (roleId.equals(InitRoleConst.SUPER_ADMIN_ROLE_ID)) {
                    throw new SystemException("权限不足，无法为用户绑定超级管理员角色");
                }
                if (roleId.equals(InitRoleConst.ADMIN_ROLE_ID) && !StpUtil.getRoleList().contains(InitRoleConst.SUPER_ADMIN_ROLE)) {
                    throw new SystemException("权限不足，无法为用户绑定管理员角色");
                }

                UserRole ur = new UserRole();
                ur.setUserId(userId);
                ur.setRoleId(roleId);
                userRoleRepository.save(ur);
                log.info("绑定成功: (userId=[{}], roleId=[{}])", userId, roleId);
            } catch (Exception e) {
                failList.add(roleId);
            }
        }

        if (failList.size() < roleIds.size()) {
            log.info("有绑定成功，要更新ROLES_DB_REFRESHED和PERMISSIONS_EB_REFRESHED时间戳，以动态更新用户权限缓存");
            roleService.setRolesDbRefreshed(new Date());
            permissionService.setPermissionsDbRefreshed(new Date());
        }

        log.info("绑定失败的角色ID：{}", failList);
        return failList;
    }

    @Override
    public List<Long> cancelBind(Long userId, List<Long> roleIds) {
        log.info("[[解除绑定]]");
        Assert.notNull(userId, "userId为空");
        Assert.notNull(roleIds, "roleIds为空");
        log.info("输入参数: userId=[{}], roleIds=[{}]", userId, roleIds);

        List<Long> failList = new ArrayList<>();
        for (Long roleId : roleIds) {
            try {
                if (roleId.equals(InitRoleConst.SUPER_ADMIN_ROLE_ID)) {
                    throw new SystemException("权限不足，无法为用户取消超级管理员角色");
                }
                if (roleId.equals(InitRoleConst.ADMIN_ROLE_ID) && !StpUtil.getRoleList().contains(InitRoleConst.SUPER_ADMIN_ROLE)) {
                    throw new SystemException("权限不足，无法为用户取消管理员角色");
                }

                UserRole ur = new UserRole();
                ur.setUserId(userId);
                ur.setRoleId(roleId);
                userRoleRepository.delete(ur);
                log.info("解除绑定成功: (userId=[{}], roleId=[{}])", userId, roleId);
            } catch (Exception e) {
                failList.add(roleId);
            }
        }

        if (failList.size() < roleIds.size()) {
            log.info("有解除绑定成功，要更新ROLES_ED_REFRESHED和PERMISSIONS_DB_REFRESHED时间戳，以动态更新用户权限缓存");
            roleService.setRolesDbRefreshed(new Date());
            permissionService.setPermissionsDbRefreshed(new Date());
        }

        log.info("解除绑定失败的角色ID：{}", failList);
        return failList;
    }
}
